package ru.ifmo.votinov;

import universum.bi.*;

import java.util.*;

class CorporateMind {
    private static final int STEPS_TO_EAT = 2;
    private static final int GATHERING_LOCATIONS_COUNT = 10;

    private static CorporateMind instance;

    private Map<Integer, Float> enemyMasses;
    private Map<ConquerorBeing, List<Location>> searchLocations;
    private Map<Location, EatersColony> colonies;
    private SearchMap searchMap;
    private Map<Location, Float> energyMap;
    private Map<Location, Location> closestEnergyLocation;
    private List<Location> allSeekLocations;
    private List<ConquerorBeing> beings;
    private List<Location> gatheringLocations;
    private int turn;
    private int maxTurn;

    static void createInstance(int maxTurns) {
        instance = new CorporateMind(maxTurns);
    }

    static CorporateMind getInstance() {
        return instance;
    }

    private EatersColony getOrCreateColony(Location location) {
        if (colonies.get(location) == null) {
            colonies.put(location, new EatersColony());
        }
        return colonies.get(location);
    }

    private CorporateMind(int maxTurns) {
        turn = -1;
        searchMap = new SearchMap(3);
        energyMap = new HashMap<Location, Float>();
        closestEnergyLocation = new HashMap<Location, Location>();
        allSeekLocations = new ArrayList<Location>();
        colonies = new HashMap<Location, EatersColony>();
        beings = new ArrayList<ConquerorBeing>();
        searchLocations = new HashMap<ConquerorBeing, List<Location>>();
        enemyMasses = new HashMap<Integer, Float>();
        maxTurn = maxTurns;
    }

    int getTurn() {
        return turn;
    }

    int getMaxTurn() {
        return maxTurn;
    }

    synchronized void checkIfNewTurnStarted(BeingInterface bi, ConquerorBeing being) {
        if (bi.getTurnsCount() > turn) {
            turn = bi.getTurnsCount();
            handleNewTurnStarted(bi, being);
        }
    }

    private void init(Location startLocation) {
        if (!searchMap.isInitialized()) {
            searchMap.init(startLocation);
            allSeekLocations.addAll(searchMap);
            searchMap.remove(startLocation);
        }
    }

    void addBeing(ConquerorBeing being) {
        beings.add(being);
    }

    void removeBeing(ConquerorBeing being) {
        beings.remove(being);
    }

    private void handleNewTurnStarted(BeingInterface bi, ConquerorBeing being) {
        init(bi.getLocation(being));
        updateEnergies(bi);
        Collection<EatersColony> colonies = new ArrayList<EatersColony>(this.colonies.values());
        for (EatersColony colony : colonies) {
            colony.handleNewTurnStarted();
        }
    }

    private SearchMap getSearchMap() {
        return searchMap;
    }

    private float getEnergy(Location location) {
        Float energy = energyMap.get(location);
        return energy != null ? energy : 0;
    }

    private Location getClosestLocationWithEnergy(BeingInterface bi, ConquerorBeing being, Location location) {
        Location closestLocation = closestEnergyLocation.get(location);
        if (closestLocation == null) {
            for (PointInfo pi : bi.getNeighbourInfo(being)) {
                closestLocation = closestEnergyLocation.get(pi.getLocation());
                if (closestLocation != null) {
                    break;
                }
            }
        }
        return closestLocation;
    }

    Location getClosestLocationWithEnergy(BeingInterface bi, ConquerorBeing being) {
        return getClosestLocationWithEnergy(bi, being, bi.getPointInfo(being).getLocation());
    }

    Location getGatheringLocation(BeingInterface bi, ConquerorBeing being) {
        if (gatheringLocations == null) {
            gatheringLocations = new ArrayList<Location>(colonies.keySet());
            Collections.sort(gatheringLocations, new Comparator<Location>() {
                public int compare(Location o1, Location o2) {
                    return ((Integer)colonies.get(o2).size()).compareTo(colonies.get(o1).size());
                }
            });
            gatheringLocations = gatheringLocations.subList(0,
                    Math.min(GATHERING_LOCATIONS_COUNT, gatheringLocations.size()));
        }
        return SearchUtil.getClosestLocation(bi, bi.getLocation(being), gatheringLocations);
    }

    private void updateEnergies(BeingInterface bi) {
        for (ConquerorBeing being : beings) {
            being.updateEnergy();
            List<PointInfo> ni = bi.getNeighbourInfo(being);
            for (PointInfo pi : ni) {
                updateEnergies(bi, being, pi);
            }
            updateEnergies(bi, being, bi.getPointInfo(being));
        }
    }

    private void updateEnergies(BeingInterface bi, ConquerorBeing being, PointInfo pi) {
        float count = pi.getCount(being);
        energyMap.put(pi.getLocation(), count);
        if (count > 0) {
            updateClosestEnergyLocations(bi, pi.getLocation());
        }
    }

    private void updateClosestEnergyLocations(BeingInterface bi, Location location) {
        for (Location seekedLocation : allSeekLocations) {
            updateClosestEnergyLocations(bi, seekedLocation, location);
        }
    }

    private void updateClosestEnergyLocations(BeingInterface bi, Location seekLocation, Location location) {
        Location closestLocation = closestEnergyLocation.get(seekLocation);
        if (closestLocation == null || bi.distance(seekLocation, location) < bi.distance(seekLocation, closestLocation)) {
            closestEnergyLocation.put(seekLocation, location);
        }
    }

    synchronized void updateEnemyMasses(BeingInterface bi, ConquerorBeing being) {
        for (PointInfo pi : bi.getNeighbourInfo(being)) {
            Integer[] ids = pi.getEntities(being, null);
            for (Integer id : ids) {
                if (!bi.getOwner(being, id).equals(bi.getOwner(being, bi.getId(being)))) {
                    float mass = bi.getMass(being, id);
                    if (mass != 0) {
                        enemyMasses.put(id, mass);
                    }
                }
            }
        }
    }

    synchronized void stopSearching(ConquerorBeing being) {
        if (searchLocations.get(being) != null) {
            getSearchMap().addAll(searchLocations.get(being));
            searchLocations.put(being, null);
        }
    }

    Location getUnoccupiedEatingLocation(BeingInterface bi, ConquerorBeing being) {
        Location eatingLocation = null;
        float maxCount = 0f;
        List<Location> rll = bi.getReachableLocations(being);
        for (Location rl : rll) {
            float count = getEnergy(rl);
            if (count > maxCount && !hasEaters(rl)) {
                maxCount = count;
                eatingLocation = rl;
            }
        }
        return eatingLocation;
    }

    synchronized List<Location> getSearchLocations(BeingInterface bi, ConquerorBeing being) {
        if (searchLocations.get(being) == null || searchLocations.get(being).size() == 0) {
            List<Location> locations = getSearchLocations(bi, being, bi.getLocation(being));
            searchLocations.put(being, locations);
            getSearchMap().removeAll(locations);
        }
        return searchLocations.get(being);
    }

    synchronized boolean canSearchFromLocation(BeingInterface bi, ConquerorBeing being, Location location) {
        return getSearchLocations(bi, being, location).size() != 0;
    }

    private List<Location> getSearchLocations(BeingInterface bi, ConquerorBeing being, Location fromLocation) {
        int stepsCanMake = SearchUtil.stepsCanMake(being);
        List<Location> result = new ArrayList<Location>();
        List<Location> closestSeekLocations = SearchUtil.getClosestSeekLocations(bi, fromLocation, getSearchMap());
        closestSeekLocations.removeAll(result);
        int minSteps = Integer.MAX_VALUE;
        Location closestLocation = null;
        for (Location location : closestSeekLocations) {
            int steps = SearchUtil.movesCount(fromLocation, location, being) + SearchUtil.movesCount(
                    location, getClosestLocationWithEnergy(bi, being, location), being) + STEPS_TO_EAT;
            if (steps < minSteps) {
                minSteps = steps;
                closestLocation = location;
            }
        }
        if (minSteps <= stepsCanMake) {
            result.add(closestLocation);
            stepsCanMake -= SearchUtil.movesCount(fromLocation, closestLocation, being);
        }
        return result;
    }

    private boolean hasEaters(Location location) {
        return colonies.get(location) != null && colonies.get(location).size() > 0;
    }

    float getEnemyMass(Integer id) {
        Float enemyMass = enemyMasses.get(id);
        return enemyMass != null ? enemyMass : 0f;
    }

    void removeDefender(ConquerorBeing being, Location location) {
        getOrCreateColony(location).removeDefender(being);
    }

    Integer getTargetEnemyId(ConquerorBeing being, Location location) {
        return getOrCreateColony(location).getTargetEnemyId(being);
    }

    float getAmountToGive(ConquerorBeing being, Location location) {
        return getOrCreateColony(location).getAmountToGive(being);
    }

    ConquerorBeing getEnergyTaker(ConquerorBeing being, Location location) {
        return getOrCreateColony(location).getEnergyTaker(being);
    }

    Location getAttackLocationForDefender(BeingInterface bi, ConquerorBeing being, Location location) {
        return getOrCreateColony(location).getAttackLocationForDefender(bi, being);
    }

    void joinTheColony(ConquerorBeing being, Location location) {
        getOrCreateColony(location).addBeing(being);
    }

    void leaveColony(ConquerorBeing being, Location location) {
        getOrCreateColony(location).removeBeing(being);
    }

    synchronized boolean shouldEat(BeingInterface bi, ConquerorBeing being, Location location) {
        return getOrCreateColony(location).shouldEat(bi, being);
    }

    boolean shouldGiveEnergy(BeingInterface bi, ConquerorBeing being, Location location) {
        return getOrCreateColony(location).shouldGiveEnergy(bi, being);
    }
}
